import time
import argparse
import traceback
import threading
import logging
from config_loader import load_mqtt_config, load_plc_config
# 使用工厂类导入而不是直接导入ModbusReader
from plc_factory import PLCFactory
from mqtt_publisher import MqttPublisher
from data_cache import DataCache
from utils.logger import get_logger, set_console_log_level

# Create logger instance
logger = get_logger("main.py+main")


def plc_worker(plc_client, data_cache, plc_config, stop_event):
    """
    Worker function for PLC data collection.
    
    :param plc_client: PLC client instance
    :param data_cache: Data cache instance
    :param plc_config: PLC configuration
    :param stop_event: Event to signal thread termination
    """
    logger.info("PLC worker started")
    try:
        while not stop_event.is_set():
            # Read data from PLC with optimization and time measurement
            plc_data, read_time = plc_client.read_all_registers_optimized()
            
            # Add the read time to the data to be written back to PLC
            if plc_data is not None:
                plc_data["modbus_read_cycle"] = int(read_time * 1000)  # Convert to milliseconds
                
            if plc_data and len(plc_data) > 0:
                logger.info(f"PLC data read successfully, {len(plc_data)} registers read in {read_time:.3f}s")
                logger.debug(f"PLC data content: {plc_data}")
                
                # Write the read time back to PLC
                plc_client.write_registers_from_data(plc_data)
                
                # Put data into cache
                data_cache.put(plc_data)
            elif plc_data is not None and len(plc_data) == 0:
                logger.warning("No data read from PLC (empty result)")
            else:
                logger.error("Failed to read data from PLC, will retry...")
                
            # Wait before next poll
            if stop_event.wait(plc_config.connection.poll_interval / 1000):
                break
                
    except Exception as e:
        logger.error(f"Unexpected error in PLC worker: {str(e)}")
        logger.error(f"Traceback: {traceback.format_exc()}")
    finally:
        logger.info("PLC worker stopped")


def _get_logger_level_from_plc_data(plc_config, plc_data):
    """
    Extract logger level from PLC data
    :param plc_config: PLC configuration
    :param plc_data: PLC data dictionary
    :return: Logger level (logging.DEBUG or logging.INFO) or None if not found
    """
    logger.debug(f"Checking logger level from PLC data. PLC data keys: {list(plc_data.keys())}")
    
    # Directly check if logger_level is in the PLC data
    if "logger_level" in plc_data:
        level_value = plc_data["logger_level"]
        logger.debug(f"Logger level value from PLC: {level_value} (type: {type(level_value)})")
        if level_value == 1:
            logger.debug("Setting logger level to DEBUG")
            return logging.DEBUG
        else:
            logger.debug("Setting logger level to INFO")
            return logging.INFO
    else:
        logger.debug("logger_level not found in PLC data")
    
    logger.debug("Logger level parameter not found or not changed")
    return None


def _update_logger_level(new_level):
    """
    Update logger level
    :param new_level: New logger level
    """
    logger.debug(f"Updating logger level to: {new_level}")
    if new_level == logging.DEBUG:
        set_console_log_level(logging.DEBUG)
        logger.info("Logger level updated to DEBUG - showing debug messages in console")
    elif new_level == logging.INFO:
        set_console_log_level(logging.INFO)
        logger.info("Logger level updated to INFO - showing info and above messages in console")
    else:
        logger.warning(f"Unknown logger level: {new_level}")


def mqtt_worker(mqtt_publisher, data_cache, mqtt_config, plc_config, stop_event, publish_mode=1):
    """
    Worker function for MQTT data publishing.
    
    :param mqtt_publisher: MQTT publisher instance
    :param data_cache: Data cache instance
    :param mqtt_config: MQTT configuration
    :param plc_config: PLC configuration
    :param stop_event: Event to signal thread termination
    :param publish_mode: 1 for latest data only, 2 for all data sequentially
    """
    logger.info(f"MQTT worker started with publish mode: {publish_mode}")
    
    last_publish_time = 0
    last_logger_level = None  # Track last logger level to detect changes
    
    try:
        while not stop_event.is_set():
            # Get current publish interval from mqtt_config (which may be updated by mqtt_publisher)
            current_publish_interval = mqtt_config.publishing.publish_interval
            logger.debug(f"Current publish interval: {current_publish_interval}ms")
            
            # Publish data at intervals
            current_time = time.time()
            if current_time - last_publish_time >= current_publish_interval / 1000:
                logger.debug("Time to publish data")
                if publish_mode == 1:
                    # Mode 1: Get latest data only and clear cache
                    logger.debug("Getting latest data from cache (mode 1)")
                    plc_data = data_cache.get_latest()
                    if plc_data:
                        logger.debug(f"Got PLC data: {plc_data}")
                        # Check and update logger level if needed
                        new_logger_level = _get_logger_level_from_plc_data(plc_config, plc_data)
                        logger.debug(f"New logger level from PLC: {new_logger_level}")
                        if new_logger_level is not None and new_logger_level != last_logger_level:
                            logger.debug(f"Updating logger level from {last_logger_level} to {new_logger_level}")
                            _update_logger_level(new_logger_level)
                            last_logger_level = new_logger_level
                        else:
                            logger.debug("Logger level unchanged or not found")
                        
                        # Measure MQTT publish time
                        publish_start_time = time.time()
                        logger.debug("Attempting to publish latest data...")
                        if mqtt_publisher.publish_data(plc_data):
                            publish_time = time.time() - publish_start_time
                            last_publish_time = current_time
                            logger.info("Latest data published successfully")
                            
                            # Add publish time to PLC data and write to PLC
                            plc_data["mqtt_publish_cycle"] = int(publish_time * 1000)  # Convert to milliseconds
                            # Write the publish time back to PLC
                            mqtt_publisher.plc_client.write_registers_from_data(plc_data)
                        else:
                            logger.error("Failed to publish latest data")
                    else:
                        logger.debug("No data available in cache for publishing")
                elif publish_mode == 2:
                    # Mode 2: Get all data sequentially and clear cache
                    logger.debug("Getting all data from cache (mode 2)")
                    plc_data_list = data_cache.get_all_sequential()
                    if plc_data_list:
                        logger.debug(f"Got {len(plc_data_list)} PLC data items")
                        publish_start_time = time.time()
                        
                        # For mode 2, we check logger level from the first data item
                        if plc_data_list:
                            new_logger_level = _get_logger_level_from_plc_data(plc_config, plc_data_list[0])
                            logger.debug(f"New logger level from PLC: {new_logger_level}")
                            if new_logger_level is not None and new_logger_level != last_logger_level:
                                logger.debug(f"Updating logger level from {last_logger_level} to {new_logger_level}")
                                _update_logger_level(new_logger_level)
                                last_logger_level = new_logger_level
                            else:
                                logger.debug("Logger level unchanged or not found")
                        
                        logger.debug(f"Attempting to publish {len(plc_data_list)} data items sequentially...")
                        all_success = True
                        for plc_data in plc_data_list:
                            if not mqtt_publisher.publish_data(plc_data):
                                logger.error("Failed to publish data item")
                                all_success = False
                                
                        if all_success:
                            publish_time = time.time() - publish_start_time
                            last_publish_time = current_time
                            logger.info("All data items published successfully")
                            
                            # Add publish time to the last PLC data item and write to PLC
                            if plc_data_list:
                                last_plc_data = plc_data_list[-1]
                                last_plc_data["mqtt_publish_cycle"] = int(publish_time * 1000)  # Convert to milliseconds
                                # Write the publish time back to PLC
                                mqtt_publisher.plc_client.write_registers_from_data(last_plc_data)
                        else:
                            logger.error("Failed to publish some data items")
                    else:
                        logger.debug("No data available in cache for publishing")
                    
            # Wait a bit before checking again
            if stop_event.wait(0.1):  # Check every 100ms
                break
                
    except Exception as e:
        logger.error(f"Unexpected error in MQTT worker: {str(e)}")
        logger.error(f"Traceback: {traceback.format_exc()}")
    finally:
        logger.info("MQTT worker stopped")


def main(debug_mode=False, verbose=False):
    # If debug mode is enabled, wait for debugger to connect
    if debug_mode:
        try:
            import debugpy
            debugpy.listen(5678)
            logger.info("Waiting for debugger to connect on port 5678...")
            # debugpy.wait_for_client()  # Uncomment to force wait for debugger connection
        except ImportError:
            logger.warning("debugpy not installed, skipping debug mode")
            
    # Set console log level based on verbose flag
    if verbose:
        set_console_log_level(logging.DEBUG)
        logger.info("Verbose mode enabled - showing debug messages in console")
    else:
        set_console_log_level(logging.INFO)
        logger.info("Standard mode - showing info and above messages in console")

    # Load configuration
    try:
        mqtt_config = load_mqtt_config()
        plc_config = load_plc_config()
        logger.info("Configuration loaded successfully")
    except Exception as e:
        logger.error(f"Failed to load configuration: {str(e)}")
        return

    # Initialize data cache
    data_cache = DataCache(max_size=100)
    
    # Initialize clients using factory pattern
    try:
        plc_client = PLCFactory.create_client(plc_config.connection.protocol, plc_config)
        mqtt_publisher = MqttPublisher(mqtt_config, plc_config)
        # Set the plc_client reference in mqtt_publisher
        mqtt_publisher.plc_client = plc_client
    except Exception as e:
        logger.error(f"Failed to create PLC client: {str(e)}")
        return

    # Test connection
    if not plc_client.connect():
        logger.error("Initial PLC connection failed")
        return

    if not mqtt_publisher.connect():
        logger.error("Initial MQTT connection failed")
        return

    logger.info("All connections established successfully")
    
    # Create stop event for thread coordination
    stop_event = threading.Event()
    
    # Create and start worker threads
    plc_thread = threading.Thread(
        target=plc_worker, 
        args=(plc_client, data_cache, plc_config, stop_event),
        name="PLCWorker"
    )
    
    # For now, we use mode 1 (latest data only)
    # To use mode 2 (all data sequentially), change the publish_mode parameter to 2
    mqtt_thread = threading.Thread(
        target=mqtt_worker, 
        args=(mqtt_publisher, data_cache, mqtt_config, plc_config, stop_event, 1),
        name="MQTTWorker"
    )
    
    # Start threads
    plc_thread.start()
    mqtt_thread.start()
    
    logger.info("Worker threads started")
    
    try:
        # Keep main thread alive
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        logger.info("Program manually terminated")
    except Exception as e:
        logger.error(f"Unexpected error in main thread: {str(e)}")
        logger.error(f"Traceback: {traceback.format_exc()}")
    finally:
        # Signal threads to stop
        stop_event.set()
        
        # Wait for threads to finish
        plc_thread.join(timeout=5)
        mqtt_thread.join(timeout=5)
        
        # Clean up connections
        try:
            plc_client.disconnect()
            mqtt_publisher.disconnect()
        except Exception as e:
            logger.error(f"Error during cleanup: {str(e)}")
        
        logger.info("Program exited")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Equipment Data to MQTT Service")
    parser.add_argument("--debug", action="store_true", help="Enable debug mode")
    parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose mode to show debug messages")
    args = parser.parse_args()
    
    main(debug_mode=args.debug, verbose=args.verbose)